package client.domein.dammen;

import java.util.*;
import java.util.concurrent.*;
import java.net.*;
import java.awt.Point;
import java.awt.Color;
import client.domein.*;
import core.net.*;

/**
 * Klasse die het dammenspel beschrijft
 * @author Vanpoucke Sven
 *
 */
public class DammenGame
{
	private List<Speler> spelers;
	private Point veldGrootte;
	private Point selectedPoint;
	private Validator validator;
	private boolean gameAlive = true;
	private boolean mijnBeurt = true;
	private GameChatController gcController;
	private UDPConnection connectie;
	private InetAddress tegenstander;
	private List<DammenListener> listeners;
	private ExecutorService pool;
	       
	/**
	 * Constructor
	 * @param veldGrootte Grootte van het veld
	 * @param aantalStartPionnen Aantal initiele pionnen per speler
	 * @param tegenstander Address van tegenstander
	 * @param verzendPoort Poort waarop wordt verzonden
	 * @param ontvangPoort Poort waarop wordt ontvangen
	 * @param mijnBeurt True indien mijn beurt eerst is
	 */
	public DammenGame(Point veldGrootte, int aantalStartPionnen, InetAddress tegenstander, int verzendPoort, 
			int ontvangPoort, boolean mijnBeurt)
	{
		this.veldGrootte = veldGrootte;
		this.tegenstander = tegenstander;
		this.mijnBeurt = mijnBeurt;
		
		validator = new Validator(this);
		connectie = new UDPConnection(verzendPoort,ontvangPoort,1024);
		pool = Executors.newFixedThreadPool(1);
		pool.execute(connectie);
		
		gcController = new GameChatController(connectie, this.tegenstander);
		connectie.addUDPDataListener(new DataReceivedParser(gcController, this));
		
		spelers = new ArrayList<Speler>();
		spelers.add(new Speler(Color.BLUE, new Point(veldGrootte.x - 1, veldGrootte.y - 1), veldGrootte, aantalStartPionnen));
		spelers.add(new Speler(Color.RED, new Point(0,0), veldGrootte, aantalStartPionnen));
		listeners = new ArrayList<DammenListener>();
	}
	
	/**
	 * Methode om het spel te initialiseren
	 */
	public void init()
	{	
		for(Speler s : spelers)
			s.createPionnen();
	}
	
	/**
	 * Methode die wordt aangeroepen wanneer de speler een move doet
	 * @param newSelectedPoint EndPoint van de move
	 */
	public void doMove(Point newSelectedPoint)
	{
		Move m = new Move(selectedPoint, newSelectedPoint);
		if(validator.validateMove(m, spelers.get(0)))
		{	
			Pion huidigePion = spelers.get(0).doMove(m);
			connectie.sendPacket(m, this.tegenstander);
			checkForKing(huidigePion, 0);
			if(checkAndRemoveDouble(m, 1))
				if(checkForAnotherMove(huidigePion))
				{
					selectedPoint = newSelectedPoint;
					return;
				}
			
			if(!controleerEinde())
				connectie.sendPacket("NEXT", tegenstander);
			
			mijnBeurt = false;
			selectedPoint = null;
		}
		else
			throw new IllegalArgumentException("Illegale zet!");
			
	}
	
	/**
	 * Methode die controleert of een pion dubbel kan worden
	 * @param huidigePion Pion die moet worden gecontrolleerd
	 * @param speler Speler tot welke deze pion behoort
	 */
	public void checkForKing(Pion huidigePion, int speler)
	{
		if(huidigePion.getLocatie().y == speler * (veldGrootte.y - 1))
			huidigePion.setDubbel(true);
	}
	
	/**
	 * Methode die controleert of een pion (indien deze reeds een dubbele move heeft uitgevoerd), nog een dubbele move kan uitvoeren
	 * @param huidigePion Pion die moet worden gecontrolleer
	 * @return True indien er nog een dubbele move mogelijk is
	 */
	public boolean checkForAnotherMove(Pion huidigePion)
	{
		for(Pion p:spelers.get(1).getPionnen())
		{
			Point locatie = p.getLocatie();

			if(huidigePion.isDubbel())
			{
				if((locatie.x + 1 == huidigePion.getLocatie().x) ||
				   (locatie.x - 1 == huidigePion.getLocatie().x))
				{
					if(locatie.y == 0 || locatie.x == 0 || 
							locatie.x == this.getVeldGrootte().x - 1 ||
							locatie.y == this.getVeldGrootte().y - 1)
						return false;
					
					if(validator.isEmptyPlace(new Point(
							huidigePion.getLocatie().x + (2 * (locatie.x - huidigePion.getLocatie().x)),
							huidigePion.getLocatie().y + (2 * (locatie.y - huidigePion.getLocatie().y)))))
						return true;
				}
			}
			
			if(((locatie.x + 1 == huidigePion.getLocatie().x) ||
			   (locatie.x - 1 == huidigePion.getLocatie().x)) &&
			   (locatie.y + 1 == huidigePion.getLocatie().y))
			{
				if(locatie.y == 0 || locatie.x == 0 || locatie.x == this.getVeldGrootte().x - 1)
					return false;
				
				if(validator.isEmptyPlace(new Point(
						huidigePion.getLocatie().x + (2 * (locatie.x - huidigePion.getLocatie().x)),
						huidigePion.getLocatie().y - 2 )))
					return true;
			}
		}
		return false;
	}
	
	/**
	 * Methode die het ontvangen van een move van de tegenspeler verwerkt
	 * @param m Move van de tegenspeler
	 */
	public void moveReceived(Move m)
	{
		Point tegenstanderStart = m.getStartPoint();
		Point tegenstanderP1 = new Point();
		tegenstanderP1.x = 7 - tegenstanderStart.x;
		tegenstanderP1.y = 7 - tegenstanderStart.y;
		
		Point tegenstanderEnd = m.getEndPoint();
		Point tegenstanderP2 = new Point();
		tegenstanderP2.x = 7 - tegenstanderEnd.x;
		tegenstanderP2.y = 7 - tegenstanderEnd.y;
		
		Move tegenstanderMove = new Move(tegenstanderP1, tegenstanderP2);
		Pion huidigePion = spelers.get(1).doMove(tegenstanderMove);
		checkForKing(huidigePion, 1);
		checkAndRemoveDouble(tegenstanderMove, 0);
		controleerEinde();
		doRepaint();
	}
	
	/**
	 * Methode die het bericht "next" verwerkt en mijnbeurt op true zet
	 */
	public void next()
	{
		this.mijnBeurt = true;
	}
	
	/**
	 * Methode die controlleert of het einde van het spel bereikt is
	 * @return true indien het einde van het spel bereikt is
	 */
	private boolean controleerEinde()
	{
		if(spelers.get(0).getPionnen().size() == 0)
		{
			this.mijnBeurt = false;
			doEinde("verloren");
			return true;
		}
		if(spelers.get(1).getPionnen().size() == 0)
		{
			this.mijnBeurt = false;
			doEinde("gewonnen");
			return true;
		}
		
		return false;
	}
	
	/**
	 * Methode die controlleert of er een dubbele move voldaan is en verwijdert de tussenliggende pion
	 * @param m Move die moet worden gecontrolleerd
	 * @param tegenspeler Index van de tegenspeler (bij welke de pion moet worden verwijderd)
	 * @return true indien er een dubbele move aanwezig was en de tussenliggende pion werd verwijderd
	 */
	private boolean checkAndRemoveDouble(Move m, int tegenspeler)
	{
		if(Math.sqrt(Math.pow(m.getEndPoint().x - m.getStartPoint().x, 2)) == 2)
		{
			int temp1 = (m.getStartPoint().x < m.getEndPoint().x) ? 1 : -1;
			int temp2 = (m.getStartPoint().y < m.getEndPoint().y) ? 1 : -1;
			Point midden = new Point(
					m.getStartPoint().x + temp1,
					m.getStartPoint().y + temp2);
			spelers.get(tegenspeler).remove(midden);
			
			return true;
		}
			
		return false;
	}
	
	/**
	 * Valideert en selecteert een bepaalde plaats op het bord
	 * @param selectedPoint Plaats op het bord dat werd geselecteerd
	 * @throws IllegalArgumentException
	 */
	public void setSelectedPoint(Point selectedPoint) throws IllegalArgumentException
	{
		if(selectedPoint == null || validator.validateSelection(selectedPoint, spelers.get(0)))
			this.selectedPoint = selectedPoint;
		else

			throw new IllegalArgumentException("Selectie is niet mogelijk");
	}
	
	/**
	 * Methode om het spel af te sluiten
	 */
	public void closeGame()
	{
		if(!connectie.isClosed()) {
			connectie.sendPacket("exit", tegenstander);
		}
		pool.shutdownNow();
	}

	// getters en setters
	public List<Speler> getSpelers()
	{
		return spelers;
	}

	public Point getVeldGrootte()
	{
		return veldGrootte;
	}

	public Point getSelectedPoint()
	{
		return selectedPoint;
	}

	public boolean isGameAlive()
	{
		return gameAlive;
	}

	public void setGameAlive(boolean gameAlive)
	{
		this.gameAlive = gameAlive;
	}

	public boolean isMijnBeurt()
	{
		return mijnBeurt;
	}

	public void setMijnBeurt(boolean mijnBeurt)
	{
		this.mijnBeurt = mijnBeurt;
	}

	public GameChatController getGcController()
	{
		return gcController;
	}

	public void setGcController(GameChatController gcController)
	{
		this.gcController = gcController;
	}
	
	public void addDammenListener(DammenListener listener)
	{
		listeners.add(listener);
	}
	
	public void doRepaint()
	{
		for(DammenListener listener:listeners)
			listener.doRepaint();
	}
	
	public void doEinde(String hoe)
	{
		for(DammenListener listener:listeners)
			listener.doEinde(hoe);
	}
	
}
